Python Refresher

Variables


In [1]:
# Numbers
a = 5
b = 10
my_variable = 56
my_10_variable = 10

In [2]:
# 10variable will not work

In [3]:
# Strings
string_variable = "hello"
single_quote_variable = 'strings can have single quotes'

In [4]:
# Printing variables
print(my_variable)
print(string_variable)


56
hello

Exercise:


In [5]:
# Create two variables, var1 and var2, both with the same value.
var1 = 'hello'
var2 = var1
# Create two variables, num1 and num2, which multiply together to give 16.
num1 = 8
num2 = 2

Methods


In [6]:
# Replicate print method
def my_print_method():
    print('Hello')
    print('World')

In [7]:
my_print_method()


Hello
World

In [8]:
def my_multiply_method(number_one, number_two):
    return number_one * number_two

In [9]:
result = my_multiply_method(5, 3)
print(result)


15

In [10]:
print(my_multiply_method(5, 3))


15

Exercise:


In [11]:
# Complete the method by making sure it returns 42. .
def return_42():
    # Complete method here
    return 42

# Create a method below, called my_method, that takes two arguments and returns the result of its two arguments multiplied together.
def my_method(n1, n2):
    return n1 * n2

In [12]:
print(return_42())
print(my_method(5, 6))


42
30

List, tuplets and sets


In [13]:
# List
grades = [77, 80, 90, 95, 100]
print(len(grades))
print(sum(grades))
# Calculating average
print(sum(grades) / len(grades))
grades.append(199)


5
442
88.4

In [14]:
# Tuple
# Tuples are immutable (changed), which means we cannot increase the size of the tuple.
grades_tuple = (77, 80, 90, 95, 100)

In [15]:
# Set 
# Set is a list of unique and unordered values
grades_set = {77, 80, 192, 95, 100, 100}
# Duplicate of 100 will be removed.
print(grades_set)


{192, 100, 77, 80, 95}

In [16]:
# However, the tuple can be changed in a specific way.
# It achieved by re-assigning value for the whole tuple
print(grades_tuple)
grades_tuple = grades_tuple + (100, )
print(grades_tuple)


(77, 80, 90, 95, 100)
(77, 80, 90, 95, 100, 100)

In [17]:
# Re-assigning values in a list
print(grades[0])
grades[0] = 60
print(grades)


77
[60, 80, 90, 95, 100, 199]

In [18]:
# It cannot be done with either tuple nor set.
# Tuple is immutable
# Set is unordered.
# However, set supports adding new values.
print(grades_set)
grades_set.add(22)
print(grades_set)


{192, 100, 77, 80, 95}
{192, 100, 77, 80, 22, 95}

In [19]:
# Adding the same value to the set will not throw an error and the value will appear only once since set contains only unique values
print(grades_set)
grades_set.add(3)
grades_set.add(3)
grades_set.add(3)
print(grades_set)


{192, 100, 77, 80, 22, 95}
{192, 3, 100, 77, 80, 22, 95}

In [20]:
# Set operations
set_one = {1, 2, 3, 4, 5}
set_two = {1, 3, 5, 7, 9, 11}

In [21]:
print(set_two.intersection(set_one))


{1, 3, 5}

In [22]:
print(set_two.union(set_one))


{1, 2, 3, 4, 5, 7, 9, 11}

In [23]:
print(set_two.difference(set_one))


{9, 11, 7}

Exercise:


In [24]:
# Create a list, called my_list, with three numbers. The total of the numbers added together should be 100.
my_list = [11, 22, 67]
# Create a tuple, called my_tuple, with a single value in it
my_tuple = (100, )
# Modify set2 so that set1.intersection(set2) returns {5, 77, 9, 12}
set1 = {14, 5, 9, 31, 12, 77, 67, 8}
set2 = {5, 77, 9, 12, 33}

Loops

For loop


In [25]:
my_string = 'hello'

# My string is iterable
# Strings, lists, sets, tuples (and many more) are iterable.
for character in my_string:
    print(character)
    
my_list = [1, 3, 5, 7, 9]

for number in my_list:
    print(number)


h
e
l
l
o
1
3
5
7
9

While loop


In [26]:
user_wants_number = True

while (user_wants_number == True):
    print(10)
    user_input = input("Shoudl we print again? (y/n) ")
    if(user_input == 'n'):
        user_wants_number = False


10
10

If statement


In [27]:
should_continue = True

if(should_continue):
    print('Hello <3')


Hello <3

"if(should_continue)" is equal to "if(should_continue == True)"


In [28]:
known_people = ["John", "Anna", "Sara"]

person = input("Enter the person you know: ")

if(person in known_people):
    print("You know {}.".format(person))
else:
    print("You don't know {}.".format(person))


You know Sara.

Exercise:


In [29]:
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Modify the method below to make sure only even numbers are returned.
def even_numbers():
    evens = []
    for number in numbers:
        if(number % 2 == 0):
            evens.append(number)
    return evens


# Modify the below method so that "Quit" is returned if the choice parameter is "q".
# Don't remove the existing code
def user_menu(choice):
    if choice == "a":
        return "Add"
    elif (choice == "q"):
        return "Quit"

In [30]:
print(even_numbers())
print(user_menu("q"))
print(user_menu("a"))


[2, 4, 6, 8]
Quit
Add

Programming exercise


In [31]:
# Ask the user for a list of people they know
# Split the string into a list
# Ask user for a name
# See if the name is in the list of people they know
# Print out "You know _" or "You do not know _"
def who_do_you_know():
    know_people_input = input("List of people you know, separated by comma: ")
    know_people_list = know_people_input.split(",")
    return know_people_list

def ask_user(know_people_list):
    new_name = input ("Give a name of a person: ")
    if(new_name in know_people_list):
        print("You know {}".format(new_name))
    else:
        print("You don't know {}".format(new_name))
        
know_people_list = who_do_you_know()
ask_user(know_people_list)


You know Sara

List comprehension


In [32]:
my_list = [0, 1, 2, 3, 4]
an_equal_list = [x for x in range(5)]

multiply_list = [x * 3 for x in range(5)]

In [33]:
new_list = [n for n in range(10) if (n % 2) == 0]
print(new_list)


[0, 2, 4, 6, 8]

In [34]:
people_you_know_list = ["Rolf", " John", "anna", "GREG"]
normalised_people_list = [person.strip().lower() for person in people_you_know_list]
print(normalised_people_list)


['rolf', 'john', 'anna', 'greg']

Dictionaries


In [35]:
my_dict = {'name': 'Wojciech',
           'age': 26, 
           'grades' : [90, 100, 95]}
# Dictionary can have a numeric key 
another_dict = {1 : 25, 
                2 : 50, 
                3 : 75}

In [36]:
my_dict['name']


Out[36]:
'Wojciech'

In [37]:
my_dict.get('name')


Out[37]:
'Wojciech'

In [38]:
# We can have dictionary inside a dictionary
dict_in_dict = {
    'dict' : {
        'name' : 'dictionary'
    }
}

In [39]:
print(my_dict)
my_dict['grades'][0] = 95
print(my_dict)


{'name': 'Wojciech', 'age': 26, 'grades': [90, 100, 95]}
{'name': 'Wojciech', 'age': 26, 'grades': [95, 100, 95]}

In [40]:
help(dict)


Help on class dict in module builtins:

class dict(object)
 |  dict() -> new empty dictionary
 |  dict(mapping) -> new dictionary initialized from a mapping object's
 |      (key, value) pairs
 |  dict(iterable) -> new dictionary initialized as if via:
 |      d = {}
 |      for k, v in iterable:
 |          d[k] = v
 |  dict(**kwargs) -> new dictionary initialized with the name=value pairs
 |      in the keyword argument list.  For example:  dict(one=1, two=2)
 |  
 |  Methods defined here:
 |  
 |  __contains__(self, key, /)
 |      True if D has a key k, else False.
 |  
 |  __delitem__(self, key, /)
 |      Delete self[key].
 |  
 |  __eq__(self, value, /)
 |      Return self==value.
 |  
 |  __ge__(self, value, /)
 |      Return self>=value.
 |  
 |  __getattribute__(self, name, /)
 |      Return getattr(self, name).
 |  
 |  __getitem__(...)
 |      x.__getitem__(y) <==> x[y]
 |  
 |  __gt__(self, value, /)
 |      Return self>value.
 |  
 |  __init__(self, /, *args, **kwargs)
 |      Initialize self.  See help(type(self)) for accurate signature.
 |  
 |  __iter__(self, /)
 |      Implement iter(self).
 |  
 |  __le__(self, value, /)
 |      Return self<=value.
 |  
 |  __len__(self, /)
 |      Return len(self).
 |  
 |  __lt__(self, value, /)
 |      Return self<value.
 |  
 |  __ne__(self, value, /)
 |      Return self!=value.
 |  
 |  __new__(*args, **kwargs) from builtins.type
 |      Create and return a new object.  See help(type) for accurate signature.
 |  
 |  __repr__(self, /)
 |      Return repr(self).
 |  
 |  __setitem__(self, key, value, /)
 |      Set self[key] to value.
 |  
 |  __sizeof__(...)
 |      D.__sizeof__() -> size of D in memory, in bytes
 |  
 |  clear(...)
 |      D.clear() -> None.  Remove all items from D.
 |  
 |  copy(...)
 |      D.copy() -> a shallow copy of D
 |  
 |  fromkeys(iterable, value=None, /) from builtins.type
 |      Returns a new dict with keys from iterable and values equal to value.
 |  
 |  get(...)
 |      D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None.
 |  
 |  items(...)
 |      D.items() -> a set-like object providing a view on D's items
 |  
 |  keys(...)
 |      D.keys() -> a set-like object providing a view on D's keys
 |  
 |  pop(...)
 |      D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
 |      If key is not found, d is returned if given, otherwise KeyError is raised
 |  
 |  popitem(...)
 |      D.popitem() -> (k, v), remove and return some (key, value) pair as a
 |      2-tuple; but raise KeyError if D is empty.
 |  
 |  setdefault(...)
 |      D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
 |  
 |  update(...)
 |      D.update([E, ]**F) -> None.  Update D from dict/iterable E and F.
 |      If E is present and has a .keys() method, then does:  for k in E: D[k] = E[k]
 |      If E is present and lacks a .keys() method, then does:  for k, v in E: D[k] = v
 |      In either case, this is followed by: for k in F:  D[k] = F[k]
 |  
 |  values(...)
 |      D.values() -> an object providing a view on D's values
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __hash__ = None

Exercise


In [41]:
# Create a variable called student, with a dictionary.
# The dictionary must contain three keys: 'name', 'school', and 'grades'.
# The values for each must be 'Jose', 'Computing', and a tuple with the values 66, 77, and 88.
student = {'name' : 'Jose', 
           'school' : 'Computing', 
           'grades' : (66, 77, 88)}

# Assume the argument, data, is a dictionary.
# Modify the grades variable so it accesses the 'grades' key of the data dictionary.
def average_grade(data):
    # Change below
    grades = data['grades']
    return sum(grades) / len(grades)


# Implement the function below
# Given a list of students (dictionaries), calculate the average grade of the class
# You must add all the grades of all the students together
# You must also count how many grades there are in total in the entire list
def average_grade_all_students(student_list):
    total = 0
    count = 0
    for student in student_list:
        # Implement here
        total += sum(student['grades'])
        count += len(student['grades'])
    return total / count

Classes and objects


In [42]:
lottery_player_dict = {
    'name' : 'John',
    'numbers' : (5, 9, 12, 3, 1, 21)
}
print(lottery_player_dict['name'])
print(lottery_player_dict['numbers'])


John
(5, 9, 12, 3, 1, 21)

In [43]:
class LotteryPlayer:
    def __init__(self):
        self.name = 'John'
        self.numbers = (5, 9, 12, 3, 1, 21)

In [44]:
player = LotteryPlayer()
print(player.name)
print(player.numbers)


John
(5, 9, 12, 3, 1, 21)

Dictionary cannot do operations on its own data. However, we can define methods accessing that data.


In [45]:
class LotteryPlayer:
    def __init__(self):
        self.name = 'John'
        self.numbers = (5, 9, 12, 3, 1, 21)
    
    def total(self):
        return sum(self.numbers)
    
player = LotteryPlayer()
print(player.total())


51

Objects not only have data, but can have methods as well.


In [46]:
# player_one and player_two are two different instances of the same class
player_one = LotteryPlayer()
player_two = LotteryPlayer()
print(player_one)
print(player_two)


<__main__.LotteryPlayer object at 0x0000012ED8ECFF28>
<__main__.LotteryPlayer object at 0x0000012ED8ECFEF0>

In [47]:
print(player_one.name == player_two.name)


True

In [48]:
class LotteryPlayer:
    def __init__(self, name):
        self.name = name
        self.numbers = (5, 9, 12, 3, 1, 21)
    
    def total(self):
        return sum(self.numbers)
    
player_one = LotteryPlayer('John')
player_two = LotteryPlayer('Sara')

In [49]:
print(player_one.name == player_two.name)


False

Use case : Student class


In [50]:
class Student():
    def __init__(self, name, school):
        self.name = name
        self.school = school
        self.marks = []
    def avg_marks(self):
        return sum(self.marks) / len(self.marks)
    
anna = Student("Anna", "KTH")
anna.marks.append(90)
print(anna.marks)
anna.marks.append(70)
print(anna.marks)
print(anna.avg_marks())


[90]
[90, 70]
80.0

Exercise


In [51]:
class Store:
    def __init__(self, name):
        # You'll need 'name' as an argument to this method.
        # Then, initialise 'self.name' to be the argument, and 'self.items' to be an empty list.
        self.name = name
        self.items = []
    
    def add_item(self, name, price):
        # Create a dictionary with keys name and price, and append that to self.items.
        item_dict = {'name' : name, 
                     'price' : price}
        self.items.append(item_dict)
        

    def stock_price(self):
        # Add together all item prices in self.items and return the total.
        total_sum = 0
        for item in self.items: 
            total_sum += item['price']
        return total_sum

In [52]:
Asda = Store('ASDA')

In [53]:
Asda.add_item('Something', 33)
Asda.add_item('Something2', 34)

In [54]:
print(Asda.items)


[{'name': 'Something', 'price': 33}, {'name': 'Something2', 'price': 34}]

In [55]:
Asda.items[:]


Out[55]:
[{'name': 'Something', 'price': 33}, {'name': 'Something2', 'price': 34}]

In [56]:
Asda.stock_price()


Out[56]:
67

@classmethod and @staticmethod


In [57]:
class Student():
    def __init__(self, name, school):
        self.name = name
        self.school = school
        self.marks = []
    def avg_marks(self):
        return sum(self.marks) / len(self.marks)
    
    # Self can be excluded from passing to the method
    # However, we need to pass the class, instead of the object
    @classmethod
    def go_to_school(cls):
        # A very generic method
        # In this scenario, since, self is not used - it does not matter on which object it is called on.
        # Self is always passed
        print("I'm going to school.")
        print("I'm {}.".format(cls))
        # In some cases passing the class is useful,
        # Otherwise, we should use @staticmethod
    
    @staticmethod
    def back_from_school():
        print("I'm on my way back from school.")
    
anna = Student("Anna", "KTH")
anna.go_to_school()
anna.back_from_school()


I'm going to school.
I'm <class '__main__.Student'>.
I'm on my way back from school.

In [58]:
# Static method can be called directly from the class
print('Using static method:')
Student.back_from_school()


Using static method:
I'm on my way back from school.

Exercise:


In [59]:
class Store:
    def __init__(self, name):
        self.name = name
        self.items = []

    def add_item(self, name, price):
        self.items.append({
            'name': name,
            'price': price
        })

    def stock_price(self):
        total = 0
        for item in self.items:
            total += item['price']
        return total

    @classmethod
    def franchise(cls, store):
        # Return another store, with the same name as the argument's name, plus " - franchise" 
        return cls(store.name + " - franchise")
    
    @staticmethod
    def store_details(store):
        # Return a string representing the argument
        # It should be in the format 'NAME, total stock price: TOTAL'
        return '{}, total stock price: {}'.format(store.name, int(store.stock_price()))

In [60]:
store = Store("Store1")
store2 = Store("Store2")
store2.add_item("Keyboard", 160)

print(Store.franchise(store))
print(Store.franchise(store2))

print(Store.store_details(store))
print(Store.store_details(store2))


<__main__.Store object at 0x0000012ED8E8AF28>
<__main__.Store object at 0x0000012ED8E8AC88>
Store1, total stock price: 0
Store2, total stock price: 160

Inheritance


In [61]:
class Student():
    def __init__(self, name, school):
        self.name = name
        self.school = school
        self.marks = []
        
    def avg_marks(self):
        return sum(self.marks) / len(self.marks)
    
    def friend(self, friend_name):
        # Return a new Student called 'friend_name in the same school as self.
        return (Student(friend_name, self.school))
    
anna = Student("Anna", "KTH")

friend = anna.friend("Johan")
print(friend.name)
print(friend.school)


Johan
KTH

In [62]:
# Class Working student contains everything from class Student.
class WorkingStudent(Student):
    def __init__(self, name, school, salary):
        super().__init__(name, school)
        self.salary = salary
        '''
        That is equal to:
        self.name = name
        self.school = school
        self.marks = []
        
        self.salary = salary
        '''

In [63]:
anna = WorkingStudent("Anna", "KTH", "100")
print(anna.name)
print(anna.school)
print(anna.salary)


Anna
KTH
100

*args and **kwargs


In [64]:
def my_method(arg1, arg2):
    return arg1 + arg2

my_method(5, 6)


Out[64]:
11

In [65]:
def my_long_method(arg1, arg2, arg3, arg4, arg5, arg6, arg7):
    return arg1 + arg2 + arg3 + arg4 + arg5 + arg6 + arg7

In [66]:
def my_list_method(list_arg):
    return sum(list_arg)

In [67]:
my_long_method(1, 2, 3, 4, 5, 6, 7)


Out[67]:
28

In [68]:
my_list_method([1, 2, 3, 4, 5, 6, 7])


Out[68]:
28

In [69]:
# *args
def addition_simplified(*args):
    # args are converter to a list
    # but we do not need to pass to it a list.
    return sum(args)

In [70]:
addition_simplified(1, 2, 3)


Out[70]:
6

In [71]:
def what_are_kwargs(*args, **kwargs):
    print(args)
    print(kwargs)

In [72]:
what_are_kwargs(1, 2, 3)
# That returns a tuple of arguments 
# and an empty set.


(1, 2, 3)
{}

In [73]:
what_are_kwargs(1, 2, 3, name = 'Wojciech', location = 'UK')


(1, 2, 3)
{'name': 'Wojciech', 'location': 'UK'}

Passing functions as parameters


In [74]:
def methodception(another):
    return another()

def add_two_numbers():
    return 33 + 77

print(methodception(add_two_numbers))


110

In [75]:
# Lambda (anonymous) function
print(methodception(lambda: 33 + 77))


110

In [76]:
# Example of using native filter function
my_list = [13, 56, 77, 484]

list(filter(lambda x : x != 13, my_list))


Out[76]:
[56, 77, 484]

In [77]:
(lambda x : x * 3)(5)


Out[77]:
15

Decorators in Python

A decorator is a function that is called before the given decorated function.


In [78]:
import functools

In [79]:
def my_decorator(f):
    @functools.wraps(f)
    def functiona_that_runs_f():
        print("In the decorator")
        f()
        print("After the decorator")
    return functiona_that_runs_f

In [80]:
@my_decorator
def my_function():
    print("Something done here inside the function!")
    
my_function()


In the decorator
Something done here inside the function!
After the decorator

Advanced decorators


In [81]:
def my_decorator(f):
    @functools.wraps(f)
    def functiona_that_runs_f():
        print("In the decorator")
        f()
        print("After the decorator")
    return functiona_that_runs_f

def decorator_with_arguments(number):
    def my_decorator(f):
        @functools.wraps(f)
        # Adding *args, **kwargs so if the function uses that, the decorator will accept it as well.
        def function_that_runs_f(*args, **kwargs):
            if number == 56:
                print("Got 56.")
            print("Something1")
            f(*args, **kwargs)
            print("Something2")
        return function_that_runs_f
    return my_decorator

In [82]:
@decorator_with_arguments(56)
def my_new_function(x):
    print("Hello")
    print("x: " + str(x))
    
my_new_function(11)


Got 56.
Something1
Hello
x: 11
Something2